﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace Common.Util
{
    public static class Linq
    {
        /// <summary>
        /// Can order data based on the name of a column
        /// </summary>
        /// <typeparam name="TSource">Type of the data</typeparam>
        /// <param name="source">Set of data</param>
        /// <param name="columnName">Column name to sort by</param>
        /// <returns>Sorted set of data</returns>
        public static IOrderedEnumerable<TSource> OrderBy<TSource>(this IEnumerable<TSource> source, string columnName)
        {
            return SortEngine<TSource>(source, columnName, true);
        }

        /// <summary>
        /// Can order data descending based on the name of a column
        /// </summary>
        /// <typeparam name="TSource">Type of the data</typeparam>
        /// <param name="source">Set of data</param>
        /// <param name="columnName">Column name to sort by</param>
        /// <returns>Sorted set of data</returns>
        public static IOrderedEnumerable<TSource> OrderByDescending<TSource>(this IEnumerable<TSource> source, string columnName)
        {
            return SortEngine<TSource>(source, columnName, false);
        }

        /// <summary>
        /// Used by OrderBy and OrderByDescending
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <param name="columnName"></param>
        /// <param name="IsAscending"></param>
        /// <returns></returns>
        public static IOrderedEnumerable<T> SortEngine<T>(this IEnumerable<T> source, string columnName, bool IsAscending = true)
        {
            const string ASCENDING = "OrderBy";
            const string DESCENDING = "OrderByDescending";

            Type enumerable = typeof(Enumerable);
            Type me = typeof(Linq);

            //prepare to generate our generic lambda expression
            ParameterExpression list = Expression.Parameter(typeof(T), "list");
            MemberExpression property = Expression.Property(list, columnName);
            MethodInfo expressionMaker = me.GetMethod("MakeExpression", BindingFlags.Static | BindingFlags.NonPublic);
            MethodInfo expressionMethod = expressionMaker.MakeGenericMethod(typeof(T), property.Type);

            //get the generic lamdba
            var lambda = expressionMethod.Invoke(null, new object[] { property, list });

            //compile it 
            var func = (lambda as LambdaExpression).Compile();

            //get the OrderBy or OrderByDescending from Enumerable
            var sortMaker =
                    (from s in enumerable.GetMethods()
                     where
                         (s.Name == ((IsAscending) ? ASCENDING : DESCENDING))
                         && (s.GetParameters().Length == 2) //this is not particularly safe, but give me a break
                     select s).First();

            //prepare the generic sort method
            MethodInfo sorter = sortMaker.MakeGenericMethod(typeof(T), property.Type);

            //execute
            return sorter.Invoke(source, new object[] { source, func }) as IOrderedEnumerable<T>;
        }

        /// <summary>
        /// Used by OrderBy and OrderByDescending
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <param name="columnName"></param>
        /// <param name="IsAscending"></param>
        /// <returns></returns>
        public static IOrderedEnumerable<T> SortEngine<T>(this IEnumerable<T> source, string columnName, string order = "ASC")
        {
            if (source == null || source.Count() == 0) return null;
            if (string.IsNullOrEmpty(order)) order = "ASC";
            order = order.Trim().ToUpper();
            if (order != "DESC" && order != "DESCENDING") order = "ASC";
            else order = "DESC";

            const string ASCENDING = "OrderBy";
            const string DESCENDING = "OrderByDescending";

            Type enumerable = typeof(Enumerable);
            Type me = typeof(Linq);

            //prepare to generate our generic lambda expression
            ParameterExpression list = Expression.Parameter(typeof(T), "list");
            MemberExpression property = Expression.Property(list, columnName);
            MethodInfo expressionMaker = me.GetMethod("MakeExpression", BindingFlags.Static | BindingFlags.NonPublic);
            MethodInfo expressionMethod = expressionMaker.MakeGenericMethod(typeof(T), property.Type);

            //get the generic lamdba
            var lambda = expressionMethod.Invoke(null, new object[] { property, list });

            //compile it 
            var func = (lambda as LambdaExpression).Compile();

            //get the OrderBy or OrderByDescending from Enumerable
            var sortMaker =
                    (from s in enumerable.GetMethods()
                     where
                         (s.Name == ((order.ToUpper() == "ASC") ? ASCENDING : DESCENDING))
                         && (s.GetParameters().Length == 2) //this is not particularly safe, but give me a break
                     select s).First();

            //prepare the generic sort method
            MethodInfo sorter = sortMaker.MakeGenericMethod(typeof(T), property.Type);

            //execute
            return sorter.Invoke(source, new object[] { source, func }) as IOrderedEnumerable<T>;
        }

        /// <summary>
        /// This will be used in conjuction with Reflection to create a generic expression who's types we don't
        /// know at compile time
        /// </summary>
        /// <typeparam name="P"></typeparam>
        /// <typeparam name="R"></typeparam>
        /// <param name="property"></param>
        /// <param name="list"></param>
        /// <returns></returns>
        private static Expression<Func<P, R>> MakeExpression<P, R>(MemberExpression property, ParameterExpression list)
        {
            return Expression.Lambda<Func<P, R>>(property, new ParameterExpression[] { list });
        }

        /// <summary>
        /// Sort data with self relation Parent-Child
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="listInput"></param>
        /// <param name="keyField"></param>
        /// <param name="parentField"></param>
        /// <param name="displayField"></param>
        /// <param name="symbol"></param>
        /// <returns></returns>
        public static IEnumerable<TEntity> RecursivelyData<TEntity>(this IEnumerable<TEntity> listInput, string keyField, string parentField, string displayField, string symbol = "") where TEntity : class
        {
            List<TEntity> mylist = new List<TEntity>();
            foreach (var entity in listInput)
            {
                TEntity enti = null;
                Copy<TEntity>(entity, out enti);
                mylist.Add(enti);
            }

            //Get property infor of key field & parent field
            Type myType = typeof(TEntity);
            var props = myType.GetProperties();
            PropertyInfo keyInfo = null;
            PropertyInfo parentInfo = null;
            PropertyInfo displayInfo = null;
            foreach (var inf in props)
            {
                string proname = inf.Name;

                if (keyInfo == null && proname.ToUpper() == keyField.ToUpper()) keyInfo = inf;
                else if (parentInfo == null && proname.ToUpper() == parentField.ToUpper()) parentInfo = inf;
                else if (displayInfo == null && proname.ToUpper() == displayField.ToUpper()) displayInfo = inf;

                if (keyInfo != null && parentInfo != null && displayInfo != null) break;
            }

            List<TEntity> result = mylist.ToList();
            foreach (var item in mylist)
                VariousLevels(ref result, symbol, item, string.Empty, keyInfo, parentInfo, displayInfo);
            return result;
        }

        /// <summary>
        /// Recursively to shorting with list
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="listEntity"></param>
        /// <param name="symbol"></param>
        /// <param name="Parent"></param>
        /// <param name="ConstSymbol"></param>
        /// <param name="keyInfo"></param>
        /// <param name="parentInfo"></param>
        /// <param name="displayInfo"></param>
        private static void VariousLevels<TEntity>(ref List<TEntity> listEntity, string symbol, TEntity Parent, string ConstSymbol, PropertyInfo keyInfo, PropertyInfo parentInfo, PropertyInfo displayInfo)
        {
            if (listEntity == null) return;
            if (keyInfo == null || parentInfo == null || displayInfo == null) return;

            TEntity[] entitiesArray = new TEntity[listEntity.Count];
            listEntity.CopyTo(entitiesArray, 0);

            for (int i = 0; i < entitiesArray.Length; i++)
            {
                TEntity entity = entitiesArray[i];

                string keyParentValue = keyInfo.GetValue(Parent).ToString();
                string keyValue = keyInfo.GetValue(entity).ToString();
                //----
                object parentValueO = parentInfo.GetValue(entity);
                string parentValue = (parentValueO == null ? null : parentValueO.ToString());
                //----
                object displayValueO = displayInfo.GetValue(entity);
                string displayValue = (displayValueO == null ? null : displayValueO.ToString());

                if (parentValue == keyParentValue)
                {
                    displayInfo.SetValue(entity, symbol + displayValue);
                    listEntity.Remove(entity);
                    listEntity.Insert(listEntity.IndexOf(Parent) + 1, entity);
                    VariousLevels(ref listEntity, symbol + ConstSymbol, entity, ConstSymbol, keyInfo, parentInfo, displayInfo);
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="sorce"></param>
        /// <param name="dest"></param>
        private static void Copy<TEntity>(TEntity sorce, out  TEntity dest)
        {
            Type myType = typeof(TEntity);
            var prop = myType.GetProperties();
            dest = (TEntity)Activator.CreateInstance(myType);
            foreach (var inf in prop)
            {
                try
                {
                    object value = inf.GetValue(sorce);
                    inf.SetValue(dest, value, null);
                }
                catch
                {
                }
            }
        }
    }
}
